package com.gateway.video.container;

import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;

import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * serviceLoad在多并发里面有问题
 */
@Slf4j
public class ModuleImpl {
    @Autowired
    private ConfigurableApplicationContext applicationContext;

    static final String KEY_MODS_DIR = "mods.dir";

    static final String MOD_ROOT_DIR = "./mods";
    static final String LIB_DIR = "lib";
    static final String JAR_SUFFIX = ".jar";

    private Map<String, Module> modules = Collections.synchronizedMap(new HashMap<>());
    private ReentrantLock lock = new ReentrantLock();

    public void loadAll()  throws Throwable {
        var module2Loader = new HashMap<Module, ClassLoader>();

        // 加载所有实现了module的模块
        for (Module module : ServiceLoader.load(Module.class, getClass().getClassLoader())) {
            module2Loader.put(module, getClass().getClassLoader());
        }

        var homeDir = Paths.get(System.getProperty("user.dir"));
        Path modRootDir = null;
        if (!Strings.isNullOrEmpty(System.getProperty(KEY_MODS_DIR))) {
            modRootDir = Paths.get(System.getProperty(KEY_MODS_DIR));
        } else {
            modRootDir = homeDir.resolve(MOD_ROOT_DIR);
        }
        if (Files.exists(modRootDir) && Files.isDirectory(modRootDir)) {
            for (var modDir : Files.list(modRootDir).collect(Collectors.toList())) {
                module2Loader.putAll(find(modDir));
            }
        }

        List<Module> ordered = new ArrayList<>(module2Loader.keySet());
        AnnotationAwareOrderComparator.sort(ordered);
        for (var module : ordered) {
            load(module, module2Loader.get(module));
        }
        for (var module : ordered) {
            if (module instanceof ModuleLifecycle) {
                ((ModuleLifecycle)module).loadCompleted();
            }
        }
    }

    protected Map<Module, ClassLoader> find(Path modDir) throws Throwable{
        var module2Loader = new HashMap<Module, ClassLoader>();

        var libDir = modDir.resolve(LIB_DIR);
        if (Files.exists(libDir) && Files.isDirectory(libDir)) {
            List<URL> urlList = new ArrayList<>();
            for (var jarFile : Files.list(libDir).collect(Collectors.toList())) {
                var fileName = jarFile.getFileName().toString();
                if (fileName.endsWith(JAR_SUFFIX)) {
                    urlList.add(jarFile.toUri().toURL());
                }
            }

            var modLoader = new ModuleClassLoader(modDir.getFileName().toString(),
                    urlList.toArray(new URL[urlList.size()]),
                    getClass().getClassLoader());
            for (var module : ServiceLoader.load(Module.class, modLoader)) {
                module2Loader.put(module, modLoader);
            }
        }

        return module2Loader;
    }

    protected void load(Module module, ClassLoader loader)  throws Throwable {
        var moduleId = module.getClass().getName();

        lock.lock();

        try{
            if(modules.containsKey(moduleId)) {
                throw new IllegalStateException(String.format("Module [{}] already exists.", moduleId));
            }

            applicationContext.getBeanFactory().autowireBean(module);

            modules.put(moduleId, module);

            var threadClassLoader = Thread.currentThread().getContextClassLoader();
            if (module instanceof ModuleLifecycle) {
                Thread.currentThread().setContextClassLoader(loader);
                try {
                    ((ModuleLifecycle) module).onLoad();
                } finally {
                    Thread.currentThread().setContextClassLoader(threadClassLoader);
                }
            }

            if (log.isInfoEnabled()) {
                log.info("Loaded module [{}]", moduleId);
            }
        }finally {
            lock.unlock();
        }
    }

    static class ModuleClassLoader extends URLClassLoader {
        private String name;

        public ModuleClassLoader(String name, URL[] urls, ClassLoader parent) {
            super(urls, parent);

            this.name = name;
        }

        public String getName() {
            return name;
        }
    }
}
